package intopo.game.impl.givetake;

import org.w3c.dom.Element;
import cern.jet.random.engine.RandomEngine;
import cern.jet.random.engine.MersenneTwister;

import java.io.PrintStream;
import java.util.Scanner;
import util.xml.*;

/**
 * This class represents a Give-Take game that computes the payoff obtained by players by simulating a certain number of iterations.
 * <p> Since this an iterated game, there is third element which is the
 * interface that provides the methods to query the state of a game. The
 * implementation of this interface is static, so the game has a maximum
 * number of iterations.
 *
 * <h3> Configuration File </h3>
 * 
 * @author <a href="mailto:plmariano@gmail.com">Pedro Lopes da Silva Mariano</a>
 * @version 1.0
 * @see <aGiveTakeStrategylabmag.di.fc.ul.pt/~plsm/research/givetake/">Give-Take applet</a>
 * @see GiveTakeStrategy
 * @see GiveTakeHistory
 */
public class GiveTakeSimulationGame
	extends GiveTakeGame
{
	/**
	 * 
	 */
	static String CONSTRUCTOR_SIM_TAG = "GiveTakeSimulationGame";
	/**
	 * Maximum number of iterations a Give-Take game. The implementation of
	 * the interface {@code GiveTakeGameHistory} is static therefore the
	 * information about the iterations is stored in a fixed size array.
	 */
	public static final int MAX_ITERATIONS = 1000;

	/**
	 * Random number generator used when calculating the probability of
	 * occuring one more iteration.
	 */
	private static final RandomEngine random = new MersenneTwister (new java.util.Date ());

	/**
	 * Probability of occurring one more iteration/round of the game.
	 */
	private final float oneMoreRoundProbability;

	/**
	 * Minimum number of iterations a Give-Take game has.
	 */
	private final int minimumNumberOfIterations;

	/**
	 * Construct a {@code GiveTakeGame} with the given game parameters.

	 * @param giveBonus the bonus obtained by playing the give action
	 * @param takePerformerCost the cost of performing the take action
	 * @param takeSubjectCost the cost payed by the subject of the take action
	 */
	public GiveTakeSimulationGame (float giveBonus,
								float takePerformerCost,
								float takeSubjectCost,
								float oneMoreRoundProbability, int minimumNumberOfIterations)
	{
		super (giveBonus, takePerformerCost, takeSubjectCost);

		assert 0 <= oneMoreRoundProbability && oneMoreRoundProbability <= 1;
		assert minimumNumberOfIterations > 0;

		this.oneMoreRoundProbability = oneMoreRoundProbability;
		this.minimumNumberOfIterations = minimumNumberOfIterations;
	}
	/**
	 *
	 * @param scanner
	 */
	public GiveTakeSimulationGame (Scanner scanner)
	{
		this (scanner.nextFloat (), scanner.nextFloat (), scanner.nextFloat (), scanner.nextFloat (), scanner.nextInt ());
	}
	/**
	 * Construct a {@code GiveTakeGame} object whose fields are taken from
	 * the specified XML element. This element must have the following
	 * attributes:

	 * <ul><li>{@code resourceValue} a float number</li>

	 * <li>{@code giveBonus} a float number</li>

	 * <li>{@code takePerformerCost} a float number</li>

	 * <li>{@code takeSubjectCost} a float number</li>

	 * <li>{@code oneMoreRoundProbability} a float number between 0 and 1</li>

	 * <li>{@code minimumNumberOfIterations} a positive integer number</li></ul>

	 * <p>The constructor doest not check if there any extra tags or attributes
	 * in {@code element} element.

	 * @param element the XML element where the required attributes are
	 * searched.

	 * @throws XMLParseException if the XML element does not have the
	 * required attributes or an error occured while parsing a required
	 * attribute value.
	 */
	public GiveTakeSimulationGame (Element element)
		throws XMLParseException
	{
		this
			(AttributeParser.getDouble ().parse (element, "giveBonus").floatValue (),
			 AttributeParser.getDouble ().parse (element, "takePerformerCost").floatValue (),
			 AttributeParser.getDouble ().parse (element, "takeSubjectCost").floatValue (),
			 AttributeParser.get (0.0, 1.0).parse (element, "oneMoreRoundProbability").floatValue (),
			 AttributeParser.get (0, Integer.MAX_VALUE).parse (element, "minimumNumberOfIterations"));

// 		AttributeParser<Double> apPayoff = AttributeParser.getDouble ();
// 		resourceValue = apPayoff.parse (element, "resourceValue").floatValue ();
// 		giveBonus = apPayoff.parse (element, "giveBonus").floatValue ();
// 		takePerformerCost = apPayoff.parse (element, "takePerformerCost").floatValue ();
// 		takeSubjectCost = apPayoff.parse (element, "takeSubjectCost").floatValue ();

// 		AttributeParser<Double> apProb = AttributeParser.get (0.0, 1.0);
// 		oneMoreRoundProbability = apProb.parse (element, "oneMoreRoundProbability").floatValue ();

// 		AttributeParser<Integer> apTime = AttributeParser.get (0, Integer.MAX_VALUE);
// 		minimumNumberOfIterations = apTime.parse (element, "minimumNumberOfIterations");
	}

	public void play (Strategies<GiveTakeStrategy> players)
	{
//		if (players.size () != payoff.length)
//			throw new IllegalArgumentException ("The number of elements in the vectors players and payoff should be equal to 2");
//		if (players.size () != 2 && payoff.length != 2)
//			throw new IllegalArgumentException ("The number of elements in the vectors players and payoff is not 2");
//		if (payoff.length != 2)
//			throw new IllegalArgumentException ("The number of elements in the vector payoff is not 2");
//		if (players.size () != 2)
//			throw new IllegalArgumentException ("The number of elements in the vector players is not 2");

		GiveTakeHistory gth = new GiveTakeHistory ();
		GiveTakeAction []action = new GiveTakeAction [2];

		while (gth.numberOfIterations < minimumNumberOfIterations
				 || (gth.numberOfIterations < MAX_ITERATIONS
					  && random.nextFloat () <= oneMoreRoundProbability)) {
			if (players.getStrategy (gth.playerWithResource).give (gth, gth.playerWithResource))
				action [gth.playerWithResource] = GiveTakeAction.GIVE;
			else
				action [gth.playerWithResource] = GiveTakeAction.NONEG;
			if (players.getStrategy (1 - gth.playerWithResource).take (gth, 1 - gth.playerWithResource))
				action [1 - gth.playerWithResource] = GiveTakeAction.TAKE;
			else
				action [1 - gth.playerWithResource] = GiveTakeAction.NONET;
			gth.processIteration (action);

			//			gth.debug ();
		}
		for (int i = 0; i < 2; i++) {
			players.setPayoff (i, gth.score [i] / gth.numberOfIterations);
		}
	}
	public int getMinimumNumberOfIterations ()
	{
		return this.minimumNumberOfIterations;
	}
	public float getOneMoreRoundProbability ()
	{
		return this.oneMoreRoundProbability;
	}
	@Override
	public void write (PrintStream ps)
	{
		ps.print (CONSTRUCTOR_SIM_TAG);
		ps.print (' ');
		ps.print (this.giveBonus);
		ps.print (' ');
		ps.print (this.takePerformerCost);
		ps.print (' ');
		ps.print (this.takeSubjectCost);
		ps.print (' ');
		ps.print (this.oneMoreRoundProbability);
		ps.print (' ');
		ps.print (this.minimumNumberOfIterations);
	}
	public void debug ()
	{
		System.out.println ("GiveTakeGame");
		System.out.println ("bg = " + giveBonus);
		System.out.println ("cpt = " + takePerformerCost);
		System.out.println ("cst = " + takeSubjectCost);
		System.out.println ("Lowest payoff  = " + lowestPayoff ());
		System.out.println ("Highest payoff = " + highestPayoff ());
		System.out.println ("Pareto payoff = " + paretoPayoff ());
	}

	public String toString ()
	{
		String result;
		result = "bg = " + giveBonus;
		result += "\tcpt = " + takePerformerCost;
		result += "\tcst = " + takeSubjectCost;
		result += "\tLowest payoff  = " + lowestPayoff ();
		result += "\tHighest payoff = " + highestPayoff ();
		result += "\tPareto payoff = " + paretoPayoff ();
		return result;
	}

	/**
	 * This class represents the history of a <i>Give-Take</i> game:
	 * <ul>
	 * <li>Actions performed by the players</li>
	 * <li>Number of iterations of the game</li>
	 * <li>Score accumulated by the players</li>
	 * </ul>
	 */
	private class GiveTakeHistory
		implements intopo.game.impl.givetake.GiveTakeHistory
	{
		//		GiveTakeGame game;
		final boolean resourceStartsWithPlayer0;
		/**
		 * A matrix that holds the actions performed by the two players of the
		 * <i>Give-Take</i> game.
		 */
		final GiveTakeAction [][]history;
		/**
		 * Number of iterations of the corresponding game.
		 */
		int numberOfIterations;
		/**
		 * How many actions the two players have performed
		 */
		final int [][]actionCount;

		/**
		 * Indicates who has the resource.
		 */
		byte playerWithResource;

		/**
		 * Contains the accumulated score.
		 */
		final float []score;

		GiveTakeHistory (/*GiveTakeGame game*/)
		{
			//			this.game = game;
			resourceStartsWithPlayer0 = Math.random () < 0.5;
			history = new GiveTakeAction [GiveTakeSimulationGame.MAX_ITERATIONS][2];
			numberOfIterations = 0;
			actionCount = new int [2][4];
			score = new float [2];
			playerWithResource = (byte) (resourceStartsWithPlayer0 ? 0 : 1);
		}

		void processIteration (GiveTakeAction []action)
		{
			assert action.length == 2;
			if (!(action [playerWithResource].withResource
					&& !action [1 - playerWithResource].withResource)) {
				throw new IllegalArgumentException ("processAction called with incorrect parameters");
			}
			boolean resourceSwitched = false;
			for (int i = 0; i < 2; i++) {
				score [i] -= getActionCostForPerformer (action [i]);
				score [1 - i] -= /*game.*/getActionCostForSubject (action [i]);
				resourceSwitched |= action [i].switchesResource;
				history [numberOfIterations][i] = action [i];
			}
			if (resourceSwitched)
				playerWithResource = (byte) (1 - playerWithResource);
			score [playerWithResource] += /*game.*/getResourceValue ();
			numberOfIterations++;
		}

		public GiveTakeAction getActionAtIteration (int iteration, int playerIndex)
		{
			assert playerIndex == 0 || playerIndex == 1;
			assert 0 <= iteration && iteration < numberOfIterations;

			return history [iteration][playerIndex];
		}

		public GiveTakeAction getPastAction (int iteration, int playerIndex)
		{
			assert playerIndex == 0 || playerIndex == 1;
			assert 0 < iteration && iteration <= numberOfIterations;

			return history [iteration][numberOfIterations - playerIndex];
		}

		public int getIterationPastAction (GiveTakeAction action, int playerIndex)
			throws NotPlayedException
		{
			for (int i = numberOfIterations - 1; i >= 0; i--)
				if (history [i][playerIndex] == action)
					return numberOfIterations - i;
			throw new NotPlayedException (action, playerIndex);
		}

		public int getIterationsSinceLastResourceExchange ()
		{
			int result = 0;
			int i = numberOfIterations - 1;
			while (i >= 0 && !history [i][0].switchesResource && !history [i][1].switchesResource) {
				i--;
				result++;
			}
			return result;
		}

		/*
		int getPastIterationsWithResource (int playerIndex)
		{
			if (numberOfIterations == 0)
				return 0;
			int result = 1;
			int i = numberOfIterations - 1;
			while (i >= 0 && !history [i][0].switchesResource && !history [i][1].switchesResource) {
				i--;
				result++;
			}
		}
		*/

		public int getNumberIterations ()
		{
			return numberOfIterations;
		}

		public float getScore (int playerIndex)
		{
			return score [playerIndex];
		}

		void debug ()
		{
			System.out.println ("Actions:");
			int i, j;
			for (j = 0; j < 2; j++) {
				for (i = 0; i < numberOfIterations; i++) {
					System.out.print (history [i][j].code);
				}
				if (playerWithResource == j)
					System.out.print (" <--");
				System.out.println ();
			}
			for (j = 0; j < 2; j++) {
				System.out.println ("Score player " + j + " = " + score [j]);
			}
		}
	}

}
